/* AUTHOR: Chris <MA_D> Hilton
 * LICENSE: GPLv2
 * PURPOSE:  Provide a second running thread to run items.
 */

#include "second.h"
#include "threads.h"
#include "simple.h"
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>

struct elog_thread *_second = NULL;
int _in_call = 0;
int ___exit = 0;

//heres our stack of functions to call.
struct elog_scnd_func {
	int (*func) (void *);
	void *arg;
	int result;
	char *name;
	struct elog_scnd_func *next;
	time_t time;
	struct timeval clock; //clock for last run.
	timeout_t timeout;
};
struct elog_scnd_func *_end = NULL;
struct elog_scnd_func *_top = NULL;

int SCND_LOCK = 0;

//Here's our main loop:
void *__loop(void)
{
    elog_glob_reg_scnd_thread();
	while (SCND_LOCK)
		usleep(100);
		
	while (!(___exit)) {
		elog_thrd_lock(_second);
		
		if (_top) {
			_in_call = 1;
			elog_thrd_unlock(_second);
			struct timeval curTime;
			gettimeofday(&curTime, NULL);
			suseconds_t lastRun = (suseconds_t)(((suseconds_t)_top->clock.tv_sec)*1000000) + 
							(_top->clock.tv_usec);
			suseconds_t now = (suseconds_t)(((suseconds_t)curTime.tv_sec)*1000000) + 
							(curTime.tv_usec);
			suseconds_t expired = now - lastRun;

			if (expired > _top->timeout) {
				_top->time = time(NULL);
				_top->result = _top->func(_top->arg);
				gettimeofday(&(_top->clock), NULL);
			} else
				_top->result = 1;
			_top->time = 0;
			elog_thrd_lock(_second);
			_in_call = 0;
			//now the function is done
			if (_top->result) {	//put it on the end of the stack
				if (_top != _end) {
					struct elog_scnd_func *tmp = _top;
					_top = _top->next;
					tmp->next = NULL;
					_end->next = tmp;
					_end = tmp;
				}
			} else {	//delete the job.
				struct elog_scnd_func *tmp = _top;
				_top = _top->next;
				free(tmp->name);
				free(tmp);
				if (!_top)
					_end = NULL;
			}
			usleep(100);
		}
		elog_thrd_unlock(_second);
		if (!(_end))
			usleep(10000);
	}
	return NULL;
}


int elog_scnd_initialize()
{
	if (!_second) {
		SCND_LOCK = 1;
		_second = elog_thrd_launch0(__loop);
		SCND_LOCK = 0;
	} else
		return 1;	//already running
	return 0;
}
int elog_scnd_run_arg(int (*func) (void *), const char *id, void *dat)
{
	return elog_scnd_timeout_arg(func, id, dat, 0);
}
int elog_scnd_run(int (*func) (void *), const char *id)
{
	return elog_scnd_timeout(func, id, 0);
}

int elog_scnd_timeout(int (* func)(void *), const char *id, timeout_t timeout)
{
	struct elog_scnd_func *nue = malloc(sizeof *nue);
	nue->arg = NULL;	//flag
	nue->next = NULL;
	nue->result = 0;
	nue->func = func;
	nue->time = 0;
	nue->timeout = timeout*1000;
	gettimeofday(&(nue->clock), NULL);
	elog_sp_cat(&(nue->name), id, NULL);
	int c;
	for (c=0; elog_thrd_trylock(_second)
				&& c < 16; ++c)
			usleep(1000000/16);
	if (c >= 16)
		elog_scnd_kill();
	else 
		if (_top)
			if (_top->time)
				if (time(NULL) - _top->time > 180) 
					elog_scnd_kill();

	if (_end) {
		_end->next = nue;
		_end = nue;
	} else {
		_end = nue;
		_top = nue;
	}
	elog_thrd_unlock(_second);
	return 0;
}
int elog_scnd_timeout_arg(int (* func)(void *), const char *id, void *dat, 
															timeout_t timeout)
{
	struct elog_scnd_func *nue = malloc(sizeof *nue);
	nue->arg = dat;
	nue->next = NULL;
	nue->result = 0;
	nue->func = func;
	nue->time = 0;
	nue->timeout = timeout*1000;
	gettimeofday(&(nue->clock), NULL);
	elog_sp_cat(&(nue->name), id, NULL);

	int c;
	for (c=0; elog_thrd_trylock(_second)
				&& c < 16; ++c)
			usleep(100000/16);
	if (c >= 16)
		elog_scnd_kill();
	else 
		if (_top)
			if (_top->time)
				if (time(NULL) - _top->time > 180) 
					elog_scnd_kill();
	
	if (_end) {
		_end->next = nue;
		_end = nue;
	} else
		_end = _top = nue;
	elog_thrd_unlock(_second);
	return 0;
}

int elog_scnd_remove(const char *id)
{
	struct elog_scnd_func *p = _top;
	if (!p)
		return 1;
	while (p->next) {
		if (strcmp(id, p->next->name) == 0)
			break;
		p = p->next;
	}
	if (p->next) {
		struct elog_scnd_func *q = p->next;
		p = p->next->next;
		free(q->name);
		free(q);
	} else
		return 1;
	return 0;
}
int elog_scnd_still(const char *id)
{
	struct elog_scnd_func *p = _top;
	if (!p)
		return 0;
	while (p) {
		if (strcmp(id, p->name) == 0)
			break;
		p = p->next;
	}
	if (p)
		return 1;
	return 0;
}

int elog_scnd_kill()
{
	elog_wrn_print("Killing second thread\n");
	if (_in_call) {
		elog_thrd_exit(_second);
		elog_thr_thread_free(_second);

	} else {
		___exit = 1;
		usleep(20000);
	}
	//remove top
	struct elog_scnd_func *p = _top;
	_top = _top->next;
	if (_end == p)
		_end = NULL;
	free(p->name);
	free(p);

	//restart
	_second = elog_thrd_launch0(__loop);
	___exit = 0;

	return 1;
}
void elog_scnd_finish(int wait)
{
	int time = 0;
	while (_top && time <= wait) {
		usleep(1000);
		time += 1000;
	}
}

#ifdef TEST
#include <time.h>
int foo(void *arg)
{
	char *abc = arg;
	struct timeval time1;
	gettimeofday(&time1, NULL);
	
	printf("%i\n", time(NULL));
	return 1;	
}

int main(void)
{
	elog_scnd_initialize();
	elog_scnd_timeout_arg(foo, "bob", "hello world", 2000);
	
	sleep(60);
	
	return 0;
}

#endif

